function PilotChartMenuGenerator ( chart,pilotChart ) {
  var generator = this;
  this.percentileValues = [70,75,80,85,90,95,100];
  this.percentileExtended = [10, 20, 30,40];
  this.previousEvents = {};
  this.currentEventPrefix = Date.now();
  this.currentEventCounter = 0;
  this.seriesMenu = false;
  this.chart = chart;
  this.pilotChart = pilotChart;

  this.dashStyles = [
      'Solid',
      'ShortDash',
      'ShortDot',
      'ShortDashDot',
      'ShortDashDotDot',
      'Dot',
      'Dash',
      'LongDash',
      'DashDot',
      'LongDashDot',
      'LongDashDotDot'
  ];
  this.liveWindows = [
    { label: "Full", type: "all" },
    { count: 1, type: "minute" },
    { count: 5, type: "minute" },
    { count: 10, type: "minute" },
    { count: 15, type: "minute" },
    { count: 30, type: "minute" },
    { count: 45, type: "minute" },
    { count: 1, type: "hour" },
    { count: 2, type: "hour" },
    { count: 3, type: "hour" },
    { count: 4, type: "hour" },
    { count: 5, type: "hour" },
    { count: 6, type: "hour" },
    { count: 12, type: "hour" },
    { count: 1, type: "day" },
    { count: 2, type: "day" },
    { count: 3, type: "day" },
    { count: 4, type: "day" },
    { count: 5, type: "day" },
    { count: 6, type: "day" },
    { count: 7, type: "day" },
    { count: 1, type: "business day" },
    { count: 2, type: "business day" },
    { count: 3, type: "business day" },
    { count: 4, type: "business day" },
    { count: 5, type: "business day" },
    { count: 6, type: "business day" },
    { count: 7, type: "business day" },
    { count: 8, type: "business day" },
    { count: 9, type: "business day" },
    { count: 10, type: "business day" },
    { count: 1, type: "week" },
    { count: 2, type: "week" },
    { count: 3, type: "week" },
    { count: 4, type: "week" },
    { count: 1, type: "month" },
    { count: 2, type: "month" },
    { count: 3, type: "month" },
    { count: 4, type: "month" },
    { count: 5, type: "month" },
    { count: 6, type: "month" },
    { count: 1, type: "year" },
    { count: 2, type: "year" },
    { count: 5, type: "year" }
  ];

  window.addEventListener( "menuEvent", function( event ) {
    if ( generator.previousEvents.length == 0 || !event.detail ) {
      logger.log( "Invalid event or no menu event handlers", event, generator.previousEvents );
      return;
    }

    var requestId = event.detail;
    logger.info( "Menu event requested id", requestId );
    var eventHandler = generator.previousEvents[ requestId ];
    if ( typeof eventHandler == "undefined" ) {
      logger.warn( "Can't find event handler", requestId, "events", generator.previousEvents );
      return;
    }

    eventHandler.call( window );
    //flushing handlers, you can only invoke once per menu generation
    this.previousEvents = null;
  });

  


  /**
   * pass in callbacks and other customization
   * menuConfig.callbacks {
   *  changeWindowPixelSize: function( width, height ) {},
   * }
   * menuConfig.mousePosition ={ x:10, y:0}
   * menuConfig.screen = {
   *   primary: { width: 1920, height: 1080 },
   *   virtualScreen: { width: 3840, height: 1080 }
   * }
   * we're going to guess at how many monitors you have, it will assume uniform monitors
   */
  this.generateMenu = function ( menuConfig ) {
    this.initEvents();
    var callbacks;
    if ( menuConfig && menuConfig.callbacks ) {
      callbacks = menuConfig.callbacks;
    } 


   /***************************************************************************
    ** actual menu main
    ***************************************************************************
    **/
    // 
    var template = [];
    //citrix version as chris has his screen stretched
    var mouseX;
    var mouseY;
    if ( menuConfig && menuConfig.mousePosition ) {
      mouseX = menuConfig.mousePosition.x;
      mouseY = menuConfig.mousePosition.y;
    }
  
    this.generateGeneralMenu( template, menuConfig );

    this.updateIds( null, template );

    return template;
  }

  this.initEvents = function() {
    this.previousEvents = {};
    this.currentEventPrefix = Date.now();
    this.currentEventCounter = 0;
  }

  this.getItemId = function( item ) {
    if ( item.i18nKey ) {
      return item.i18nKey;
    } else if ( item.label ) {
      return item.label;
    }
    return null;
  }

  this.addEvent = function( parent, menuItem ) {
    let that = this;
    if ( menuItem.submenu || ! menuItem.click ) {
      return;
    }
    

    var id = that.getItemId( menuItem );
    if ( id  && parent ) {
      id = that.getItemId( parent )  + "_" + id;
    } else  {
      this.currentEventPrefix + "-" + this.currentEventCounter++;
    }
        
    menuItem.eventId = id;
    this.previousEvents[ id ] = menuItem.click;
    menuItem.click = function() {
      window.fireMenuEvent( id );
    }
  }

  /**
   * Update the event ids 
   */
  this.updateIds = function( parent, menuItem ) {
    var current = this;
    if ( Array.isArray( menuItem ) ) {
        menuItem.forEach( function ( item ) {
          current.updateIds( parent, item );
        });
        return;
      }
     
      if ( menuItem.submenu ) {
        menuItem.submenu.forEach( function( item ) {
          current.updateIds( menuItem, item );
        });
        return;
      }
      if ( menuItem.click ) {
        current.addEvent( parent, menuItem );
      }
    
  }

  this.getWindowLabel = function ( name ) {
    switch (name) {
      case "all":
        return "Full";
      default:
        return name.charAt(0).toUpperCase() + name.slice(1);
    }
  }

}; //end PilotChartMenuGenerator Function



PilotChartMenuGenerator.prototype.setParameter = function ( key, value ) {
    this.chart.options.mergedOptions[key] = value;
}



PilotChartMenuGenerator.prototype.appendTemplate = function (template, config) {
  template.push(config);
  if ( config.submenu ) {
    return;
  }
}


PilotChartMenuGenerator.prototype.generateSingleSeriesMenu = function( seriesMenu, series ) {
  var self = this;
  var isStep = series.options.step == "left";
  var seriesSubMenu = [];
  var dashStyles = this.dashStyles;

  var widthMenu = [];
  widthMenu.push({ label: "+", type: "normal", click: function () {
      var value = series.options.lineWidth + 1.5;
      series.update({lineWidth: value});
    }
  });

  widthMenu.push({label: "-", type: "normal", click: function () {
      var value = series.options.lineWidth - 1.5;
      series.update({lineWidth: value > 0 ? value : 1.5});
    }
  });

  self.appendTemplate(seriesSubMenu, { label: "LineWidth", i18nKey: "GraphEdit_Width", enabled: true, submenu: widthMenu });

  var dashMenu = [];
  self.dashStyles.forEach(function (value) {
    dashMenu.push({label: value + "", i18nKey: "GraphEdit_Dash_" + value,type: "checkbox",
      checked: series.options.dashStyle == value || (!series.options.dashStyle && value == dashStyles[0]),
      click: function () {
        series.update({ dashStyle: value });
      }
    });
  });
  self.appendTemplate(seriesSubMenu, { label: "DashStyle", i18nKey: "GraphEdit_Dash", enabled: true, submenu: dashMenu });

  self.appendTemplate(seriesSubMenu,  {
    label: "Step", i18nKey: "GraphEdit_Step", type: "checkbox", "checked": isStep, "click": function () {
      var mode = isStep ? Highcharts.Chart.prototype.STEP_MODES.NONE : Highcharts.Chart.prototype.STEP_MODES.LEFT;
      self.chart.setSeriesStep(series.name, mode)
    }
  });
  self.appendTemplate(seriesSubMenu, {
    label: "Hide", i18nKey: "GraphEdit_SeriesHide", type: "normal", "click": function () {
      var selfSeries = series;
      selfSeries.setVisible( false, true );
    }
  });
  
  self.appendTemplate(seriesMenu, { label: series.name, submenu: seriesSubMenu });
  return isStep;
}

PilotChartMenuGenerator.prototype.generateSeriesMenu = function ( template ) {
  var generator = this;
  var chart = this.chart;
  var renderedSeries = this.chart.getRenderedSeries();

  template.push({ type: "separator" });
  var seriesMenu = [];
  var anyStep = false;

  renderedSeries.forEach(function (series) {
    anyStep = generator.generateSingleSeriesMenu( seriesMenu, series ) || anyStep;
  });

  var allSubMenu = [];
  generator.appendTemplate(allSubMenu, {
    label: "Step", i18nKey: "GraphEdit_Step", type: "checkbox", "checked": anyStep, "click": function () {
      renderedSeries.forEach(function (series) {
        var mode = anyStep ? Highcharts.Chart.prototype.STEP_MODES.NONE : Highcharts.Chart.prototype.STEP_MODES.LEFT;
        chart.setSeriesStep(series.name, mode)
      });
    }
  });


  var allMenu = { label: "All", i18nKey: "GraphEdit_All", submenu: allSubMenu }

  seriesMenu.unshift({ type: "separator" })
  seriesMenu.unshift(allMenu);
  template.push({ label: "Series", i18nKey: "GraphEdit_Series", enabled: true, submenu: seriesMenu });
}


PilotChartMenuGenerator.prototype.generateAxisMenu = function (template ){
  var generator = this;
  var axisTypeMenu = [];
  var percentileValues = this.percentileValues;
  var percentileExtended = this.percentileExtended;
  var chart = this.chart;
  var pilotChart = this.pilotChart;

  axisTypeMenu.push({ label: "Shared Axis", broadcast:true, i18nKey: "GraphEdit_SharedAxis", type: "checkbox", checked: pilotChart.axisType == AXIS_TYPE_SHARED, click: function () { pilotChart.setAxisType(AXIS_TYPE_SHARED,pilotChart) } });
  axisTypeMenu.push({ label: "Own Axis", broadcast:true, i18nKey: "GraphEdit_OwnAxis", type: "checkbox", checked: pilotChart.axisType == AXIS_TYPE_OWN, click: function () { pilotChart.setAxisType(AXIS_TYPE_OWN,pilotChart) } });
  axisTypeMenu.push({ label: "Intelligent Axis", broadcast:true, i18nKey: "GraphEdit_IntelligentAxis", type: "checkbox", checked: pilotChart.axisType == AXIS_TYPE_INTELLIGENT, click: function () { pilotChart.setAxisType(AXIS_TYPE_INTELLIGENT,pilotChart) } });
  
  var axisMenu = [];
  axisMenu.push({ label: "Axis Type", enabled: true, i18nKey: "GraphEdit_AxisType", submenu: axisTypeMenu });

  if (chart.getPercentile) {
    var expandMenu=[];
    
    percentileExtended.forEach( function ( value ) {
      expandMenu.push({
        label: value +"%",
        i18nKey: "GraphEdit_" + value + "p",
        broadcast:true,         
        type: "checkbox",
        checked: chart.getOptionParameter(KEY_PERCENTILE_EXPANSION) == value/100,
        click: function () {
          generator.setParameter(KEY_PERCENTILE_EXPANSION, parseFloat( value/100 ) );
          chart.getPercentile().zoomToPercentile();
        }
      })      
    });
    
    var percentileMenu = [];
    percentileMenu.push({ label: "Extend Ratio", i18nKey: "GraphEdit_ExtendRatio", enabled: true, submenu: expandMenu });
    percentileValues.forEach( function( value ) {
      percentileMenu.push({
        label: value +"%",
        broadcast:true,         
        i18nKey: "GraphEdit_" + value + "p",
        type: "checkbox",
        checked: chart.getOptionParameter(KEY_PERCENTILE) == value/100,
        click: function () {
          generator.setParameter(KEY_PERCENTILE, parseFloat( value/100 ) );
          chart.getPercentile().zoomToPercentile();
        }
      });
    });


      axisMenu.push({ label: "Recalculate %tile", broadcast:true, i18nKey: "GraphEdit_RecalculatePercentile",enabled: true, submenu: percentileMenu });
  }

  axisMenu.push({
    label: "Reset YAxis", broadcast:true, i18nKey: "GraphEdit_ResetYAxis", enabled: true, click: function () {
      chart.resetYAxisExtremes(false);
      chart.redraw();
    }
  });
  generator.appendTemplate(template, { label: "Axis", i18nKey: "GraphEdit_Axis",  submenu: axisMenu });
}


PilotChartMenuGenerator.prototype.createLiveWindowMenu = function (template, windows) {
 
  
  var generator = this;
  for (index = 0; index < windows.length; index++) {
    windowMenu = windows[index];
    if (windowMenu.submenu.length == 1) {
      template.push(windowMenu.submenu[0]);
    } else {
      var menuLabel = generator.getWindowLabel(windowMenu.name);
      if ( menuLabel === "Business days") menuLabel =  "Business"

      template.push({
        label: menuLabel,
        i18nKey: "GraphEdit_" + ( menuLabel === "Business day" ? "BusinessDay" : menuLabel ),
        submenu: windowMenu.submenu
      });
    }
  }
}

PilotChartMenuGenerator.prototype.createFontSizeFunction = function (targetFontSize, chart) {
  return function () {
    chart.changeFontSize(targetFontSize);
  }
}

PilotChartMenuGenerator.prototype.createFontSizeMenu = function (template ) {
  var chart = this.chart;
  var generator = this;
  var fontSizeSubmenu = [];
  var currentSize = chart.getCurrentFontSize();
  fontSizeSubmenu.push({ label: "Current: " + currentSize, i18nKey: "GraphEdit_CurrentFont"  + currentSize, i18nArgs: [ currentSize ], enabled: false })
  fontSizeSubmenu.push({ type: "separator" });

  var numberOfSizes = 5;
  var startingSize = currentSize - numberOfSizes;
  startingSize = Math.max(startingSize, 0);
  for (var index = startingSize; index - startingSize <= numberOfSizes * 2; index++) {
    var label = index + "";

    generator.appendTemplate(fontSizeSubmenu, {
      "label": label, type: "checkbox", "enabled": index != currentSize, click: generator.createFontSizeFunction(index, chart)
    });

  }

  template.push({ label: "Font Size", i18nKey: "GraphEdit_FontSize", submenu: fontSizeSubmenu });
}


PilotChartMenuGenerator.prototype.findMenuType = function (windows, windowType) {
  var index;
  for (index = 0; index < windows.length; index++) {
    if (windows[index].name == windowType) {
      return windows[index];
    }
  }

  return null;
}

PilotChartMenuGenerator.prototype.generateLiveWindowMenu = function ( template ) {
  var generator = this;
  var chart = this.chart;
  var liveWindows = this.liveWindows;
  if (liveWindows == null || liveWindows.length == 0) return;

  template.push({ label: "Rolling Window", i18nKey: "GraphEdit_RollingWindow", enabled: false });
  var windows = [];
  var index = 0;
  var currentLiveWindow = chart.getLiveWindow();

  liveWindows.forEach(function (liveWindow) {
    var type = generator.getWindowLabel(liveWindow.type);
    var label = getLiveWindowLabel(liveWindow.count, type);
    var windowOffset = getTimeOffset(liveWindow.count, liveWindow.type);
    var menuChecked = !chart.isManuallyZoomed() && currentLiveWindow == windowOffset;


    var windowMenu = generator.findMenuType(windows, type);
    if (windowMenu == null) {
      windowMenu = {
        name: type,
        submenu: []
      };
      windows.push(windowMenu);
    }     

    var typeCapitalized = liveWindow.type === "business day" ? "BusinessDay" : liveWindow.type.charAt( 0 ).toUpperCase() + liveWindow.type.slice( 1 );
    if ( liveWindow.count > 1 ) {
      typeCapitalized += "s";
    }
    var internationalizationKey  = "GraphEdit_" + ( liveWindow.type === "all"  ? "" :  "" +liveWindow.count ) + typeCapitalized;

    if ( liveWindow.type === "business day" ) {
      windowOffset = function(){
        return getTimeOffset(liveWindow.count, liveWindow.type);
      }
    }

    windowMenu.submenu.push({
      label: label,
      broadcast:true,       
      i18nKey: internationalizationKey,
      id: '' + index++,
      checked: menuChecked,
      type: "checkbox",
      click: function () {
        chart.changeLiveWindow(windowOffset, label);
        let calculatedOffset = windowOffset;
        if ( typeof calculatedOffset == "function" ) {
          calculatedOffset = calculatedOffset();
        }
        logger.info("offset: ", calculatedOffset);
        requestMoreData(Date.now() - calculatedOffset);
      }
    });
  });
  generator.createLiveWindowMenu(template, windows);
}


PilotChartMenuGenerator.prototype.createOldWindowSizeMenu = function (template, sizeFn ) {

  var labelEntry = { label: "# Horizontally", enabled: false };
  var windowMenu = { label: "Window Size (Legacy)", submenu: [labelEntry, { type: "separator" }] };

  template.push(windowMenu);

  function createClickFunction( index, innerIndex ) {
    return function() {
      sizeFn.call(window, index, innerIndex);
    };
  }

  for (var index = 1; index <= 8; index++) {
    var submenu = { label: "" + parseInt(index), submenu: [] };
    submenu.submenu.push({ label: "# Vertically", enabled: false }, { type: "separator" });
    for (var innerIndex = 1; innerIndex <= 8; innerIndex++) {
      
        submenu.submenu.push(
          {
            label: "" + parseInt(innerIndex),
            id: "size" + index + "-" + innerIndex,
            type: "checkbox",
            click: createClickFunction( index, innerIndex )
          }
        );
    }
    windowMenu.submenu.push(submenu);
  }
};



PilotChartMenuGenerator.prototype.createWindowSizeMenu = function (template, screenLayout, resizeFunction ) {
  var sizer = new PilotScreenSizer( screenLayout );
  var generator = this;
  var sizeOptions  =[];
  var windowMenu = { "label": "Window Size", i18nKey: "GraphEdit_WindowSize", "submenu": sizeOptions };

  template.push(windowMenu);

  function createSizeMenuItem( template, label, horizontal, vertical ) {
    var i18nKey = "GraphEdit_" + label;
    template.push( {
      "label" : label,
      "i18nKey" : i18nKey,
      "type" : "checkbox",
      "checked" : generator.lastSizeClickKey === i18nKey,
      "click" : function() {
        var dimensions = sizer.calculateWidthHeight( horizontal, vertical );
        resizeFunction.call( window, dimensions.width, dimensions.height );          
        generator.lastSizeClickKey = i18nKey;
      }
    });
  }
  
  createSizeMenuItem( sizeOptions, "Tiny", 7, 7 );
  createSizeMenuItem( sizeOptions, "Small", 5, 5 );
  createSizeMenuItem( sizeOptions, "Normal", 3, 3 );
  createSizeMenuItem( sizeOptions, "Large", 2, 2 );
  createSizeMenuItem( sizeOptions, "Extra Large", 1.5, 1.5 );
  createSizeMenuItem( sizeOptions, "Huge", 1, 1 );
}



PilotChartMenuGenerator.prototype.generateGeneralMenu = function( template, menuConfig ) {
  var callbacks = undefined;
  if ( menuConfig ) {
    callbacks = menuConfig.callbacks;
  }

  var chart = this.chart;
  if ( callbacks && typeof callbacks.changeWindowPixelSize == "function" && menuConfig.screen) {
    this.createWindowSizeMenu(template, menuConfig.screen, callbacks.changeWindowPixelSize );
  }

  if ( callbacks && typeof callbacks.changeWindowSize == "function" ) {
    this.createOldWindowSizeMenu(template, callbacks.changeWindowSize );
  }

  this.createFontSizeMenu(template );
  this.generateAxisMenu(template );
  
  var reloadFunction = function() {
    window.alert( "Reload callback not configured;" );
  };

  if ( callbacks && typeof callbacks.editParameters == "function") {
    this.appendTemplate(template, {
      label: "Edit Parameters", i18nKey: "GraphEdit_EditParameters", click: callbacks.editParameters
    });
  }

  if ( callbacks && typeof callbacks.reload == "function") {
    this.appendTemplate(template, {
      label: "Refresh", i18nKey: "GraphEdit_Refresh", click: callbacks.reload, broadcast: true
    });
      
  }

  this.appendTemplate(template, { type: 'separator' });

  // this.generateLiveWindowMenu( template );
  this.generateSeriesMenu( template );
  // this.generateBusinessHourMenu( template );
  
  this.appendTemplate(template, { type: 'separator' });
  this.appendTemplate(template, { label: "Reset Zoom", click:function(){
    // var axis = chart.xAxis[0];
    // var extremes =  axis.getExtremes();
    // axis.setExtremes(extremes.dataMin, extremes.dataMax, true, false);
    chart.zoomOut();
  }});
  this.appendTemplate(template, { type: 'separator' });
  this.appendTemplate(template, { label: "Close",i18nKey: "GraphEdit_CloseGraph", role: "close" });
}

PilotChartMenuGenerator.prototype.getSeriesContextMenu = function( template, mouseX, mouseY ) {
  var generator = this;
  function findFocusedSeries ( x, y ) {
    var elements = document.elementsFromPoint( mouseX, mouseY );
    var allSeries = generator.chart.getRenderedSeries();
    var matches = [];
    for ( var index = 0; index < allSeries.length; index++ ) {
      var series = allSeries[ index ];
      var seriesGroupChildren = series.group.element.children;
      //only recursing onelevel
      if ( seriesGroupChildren == null || seriesGroupChildren.length == 0 ) {
        continue;
      }
      var matched = false;
      for ( var elementIndex = 0; matched == false && elementIndex < elements.length; elementIndex++ ) {
        var pointElement = elements[ elementIndex ];
        for ( var childrenIndex = 0; childrenIndex < seriesGroupChildren.length; childrenIndex++ ) {
          var childElement = seriesGroupChildren[ childrenIndex ];
          if ( pointElement == childElement ) {
            matches.push( series );
            matched = true;
            break;
          }
        }
      }
    }
    return matches;
  }

  if ( typeof mouseX == "undefined" || typeof mouseY == "undefined" ) {
    return;
  }
  
  var matches = findFocusedSeries( mouseX, mouseY );
  if ( matches.length == 0 ) {
    return;
  }
  console.log("Found", matches.length, "series at this location", mouseX, mouseY );
  matches.forEach( function( series ) {
    generator.generateSingleSeriesMenu( template, series )
  });
  
}

/**
 * Calculations based on landscape monitors
 * @param {*} screenLayout 
 */
function PilotScreenSizer( screenLayout ) {
  this.screenLayout = screenLayout;
  this.DIMENSIONS_4k = {
    "width" : 3840,
    "height" : 2160
  };

  this.DIMENSIONS_1080p = {
    "width" : 1920,
    "height" : 1080
  };

  this.DIMENSIONS = [ this.DIMENSIONS_1080p, this.DIMENSIONS_4k ];
  //auto calculated and allow user to set/change
  this.numberOfScreens = undefined;
}


PilotScreenSizer.prototype.countScreens = function() {
  var screenLayout = this.screenLayout;
  var screens = { wide: 2, high: 2};
  var targetDimensions = this.DIMENSIONS_1080p;
  if ( screenLayout.primary.height > this.DIMENSIONS_1080p.height ) {
    targetDimensions = this.DIMENSIONS_4k;
  }
  screens.wide = Math.ceil( screenLayout.primary.width / targetDimensions.width );
  screens.high = Math.ceil( screenLayout.primary.height / targetDimensions.height );
  
  return screens;
}

/**
 * Widths and heights calculated to closest whole number smaller than it Math.Floor
 */
PilotScreenSizer.prototype.calculateWidthHeight = function( numberWidth, numberHeight ) {
  var screenLayout = this.screenLayout;
  if ( !screenLayout || !screenLayout.primary ) {
    return null;
  }
  var screens = this.countScreens( );
  var widthPerPanel = screenLayout.primary.width / numberWidth / screens.wide;
  var titleBarSize = ( ( screenLayout.titleBarSize ) ? screenLayout.titleBarSize : 0 ) * numberHeight;
  var heightPerPanel = ( screenLayout.primary.height - titleBarSize ) / numberHeight / screens.high;

  return { "width": Math.floor( widthPerPanel ), "height": Math.floor( heightPerPanel ) };
}


//========================================================================================================

function fireMenuEvent( eventId ) {
  if ( eventId === "") return;
  var event = new CustomEvent( "menuEvent", { detail: eventId } );
  window.dispatchEvent( event );
}



